// File: jmcomic-core/src/main/java/dev/jukomu/jmcomic/client/impl/AbstractJmClient.java
package dev.jukomu.jmcomic.client.impl;

import dev.jukomu.common.postman.Postman;
import dev.jukomu.common.postman.proxy.PostmanProxy;
import dev.jukomu.common.util.net.IResponse;
import dev.jukomu.jmcomic.client.JmcomicClient;
import dev.jukomu.jmcomic.config.JmModuleConfig;
import dev.jukomu.jmcomic.entity.JmFavoritePage;
import dev.jukomu.jmcomic.entity.JmPageContent;
import dev.jukomu.jmcomic.entity.JmSearchPage;
import dev.jukomu.jmcomic.exception.ExceptionUtils;
import dev.jukomu.jmcomic.exception.RequestRetryAllFailException;
import dev.jukomu.jmcomic.util.JmcomicText;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

/**
 * 对应Python的AbstractJmClient，是所有禁漫客户端实现的抽象基类。
 * 提供了域名管理、重试机制、缓存、日志等通用功能。
 */
public abstract class AbstractJmClient extends PostmanProxy implements JmcomicClient {

    protected List<String> domainList; // 域名列表
    protected final int retryTimes; // 重试次数
    protected Map<Object, Object> clientCache; // 客户端级别的缓存
    protected String username; // 用户名，用于收藏夹等操作

    /**
     * 构造函数。
     *
     * @param postman    负责实现HTTP请求的Postman实例。
     * @param domainList 禁漫域名列表。
     * @param retryTimes 重试次数。
     */
    public AbstractJmClient(Postman postman, List<String> domainList, int retryTimes) {
        super(postman);
        this.domainList = new ArrayList<>(Objects.requireNonNull(domainList, "Domain list cannot be null."));
        this.retryTimes = retryTimes;
        this.clientCache = new ConcurrentHashMap<>(); // 默认使用线程安全的Map作为缓存
    }

    /**
     * 在构造函数后执行的初始化方法。
     * 子类可以覆盖此方法以添加特定的初始化逻辑。
     */
    protected void afterInit() {
        // Default empty implementation
    }

    @Override
    public String getClientKey() {
        // 具体的key由实现类提供
        throw new UnsupportedOperationException("Subclasses must implement getClientKey()");
    }

    @Override
    public List<String> getDomainList() {
        return Collections.unmodifiableList(domainList);
    }

    @Override
    public void setDomainList(List<String> domainList) {
        this.domainList = new ArrayList<>(Objects.requireNonNull(domainList, "Domain list cannot be null."));
    }

    @Override
    public void setCacheMap(Map<Object, Object> cacheMap) {
        this.clientCache = cacheMap;
    }

    @Override
    public Map<Object, Object> getCacheMap() {
        return clientCache;
    }

    @Override
    public String ofApiUrl(String apiPath, String domain) {
        return JmcomicText.formatUrl(apiPath, domain);
    }

    @Override
    public String getHtmlDomain() throws Exception {
        return JmModuleConfig.getHtmlDomain(getRootPostman());
    }

    @Override
    public List<String> getHtmlDomainAll() throws Exception {
        return JmModuleConfig.getHtmlDomainAll(getRootPostman());
    }

    @Override
    public java.util.Set<String> getHtmlDomainAllViaGithub() throws Exception {
        return JmModuleConfig.getHtmlDomainAllViaGithub(getRootPostman());
    }

    @Override
    public boolean isGivenType(Class<? extends JmcomicClient> clientType) {
        return clientType.isInstance(this);
    }

    /**
     * 重写get方法，应用重试和域名切换逻辑。
     */
    @Override
    public IResponse get(String url, Map<String, Object> kwargs) throws Exception {
        return requestWithRetry((u, k) -> postman.get(u, k), url, kwargs);
    }

    /**
     * 重写post方法，应用重试和域名切换逻辑。
     */
    @Override
    public IResponse post(String url, Map<String, Object> kwargs) throws Exception {
        return requestWithRetry((u, k) -> postman.post(u, k), url, kwargs);
    }

    /**
     * 执行带重试机制的请求。
     *
     * @param requestFunction 执行请求的函数。
     * @param url             路径或完整URL。
     * @param kwargs          请求参数。
     * @return 响应对象IResponse。
     * @throws Exception 如果所有重试都失败。
     */
    protected <R> R requestWithRetry(RequestFunction<R> requestFunction, String url, Map<String, Object> kwargs) throws Exception {
        return requestWithRetryRecursive(requestFunction, url, kwargs, 0, 0);
    }

    /**
     * 递归实现重试和域名切换。
     */
    private <R> R requestWithRetryRecursive(RequestFunction<R> requestFunction, String url, Map<String, Object> kwargs, int domainIndex, int retryCount) throws Exception {
        if (domainIndex >= domainList.size()) {
            return fallback(requestFunction, url, domainIndex, retryCount, kwargs);
        }

        String actualUrl = url;
        Map<String, Object> actualKwargs = (kwargs != null) ? new HashMap<>(kwargs) : new HashMap<>();

        if (url.startsWith("/")) {
            // path -> url
            String domain = domainList.get(domainIndex);
            actualUrl = ofApiUrl(url, domain);
            updateRequestWithSpecifyDomain(actualKwargs, domain, false);
            JmModuleConfig.jmLog(logTopic(), decodeUrl(actualUrl));
        } else {
            // 完整URL（例如图片URL）
            updateRequestWithSpecifyDomain(actualKwargs, null, true);
        }

        if (domainIndex != 0 || retryCount != 0) {
            JmModuleConfig.jmLog("req.retry", String.format(
                    "次数: [%d/%d], 域名: [%d of %s], 路径: [%s], 参数: [%s]",
                    retryCount, retryTimes, domainIndex, domainList, actualUrl,
                    (actualUrl.contains("login") ? "#login_form#" : actualKwargs)
            ));
        }

        try {
            R resp = requestFunction.execute(actualUrl, actualKwargs);
            return raiseIfRespShouldRetry(resp);
        } catch (Exception e) {
            if (retryTimes == 0) {
                throw e;
            }
            beforeRetry(e, actualKwargs, retryCount, actualUrl);

            if (retryCount < retryTimes) {
                return requestWithRetryRecursive(requestFunction, url, kwargs, domainIndex, retryCount + 1);
            } else {
                return requestWithRetryRecursive(requestFunction, url, kwargs, domainIndex + 1, 0);
            }
        }
    }

    /**
     * 定义一个函数式接口来表示请求方法。
     */
    @FunctionalInterface
    protected interface RequestFunction<R> {
        R execute(String url, Map<String, Object> kwargs) throws Exception;
    }

    /**
     * 当所有重试都失败时的回退处理。
     *
     * @param requestFunction 请求函数
     * @param url             请求URL
     * @param domainIndex     域名索引
     * @param retryCount      重试次数
     * @param kwargs          请求参数Map
     * @return 响应对象IResponse (如果可以从回退中恢复)
     * @throws Exception 抛出异常表明请求最终失败
     */
    protected <R> R fallback(RequestFunction<R> requestFunction, String url, int domainIndex, int retryCount, Map<String, Object> kwargs) {
        String msg = String.format("请求重试全部失败: URL=[%s], 域名列表=[%s]", url, domainList);
        JmModuleConfig.jmLog("req.fallback", msg);
        ExceptionUtils.raises(msg, new HashMap<String, Object>() {{
            put("url", url);
            put("domains", domainList);
        }}, RequestRetryAllFailException.class);
        return null; // unreachable
    }

    /**
     * 请求重试前的钩子方法。
     * 默认打印异常信息。
     *
     * @param e          异常
     * @param kwargs     请求参数
     * @param retryCount 当前重试次数
     * @param url        请求URL
     */
    protected void beforeRetry(Exception e, Map<String, Object> kwargs, int retryCount, String url) {
        JmModuleConfig.jmLog("req.error", e.toString());
    }

    /**
     * 响应处理钩子，子类可覆盖此方法以根据响应决定是否需要重试。
     * 如果响应需要重试，此方法应抛出异常。
     *
     * @param resp 响应对象
     * @param <R>  响应类型
     * @return 原始响应对象
     * @throws Exception 如果响应需要重试
     */
    protected <R> R raiseIfRespShouldRetry(R resp) throws Exception {
        return resp;
    }

    /**
     * 切换域名时更新请求参数的钩子方法。
     *
     * @param kwargs  请求参数Map
     * @param domain  当前使用的域名
     * @param isImage 是否是图片请求
     */
    protected void updateRequestWithSpecifyDomain(Map<String, Object> kwargs, String domain, boolean isImage) {
        // Default empty implementation
    }

    /**
     * 获取日志主题。
     *
     * @return 日志主题字符串
     */
    protected String logTopic() {
        return getClientKey();
    }

    /**
     * 解码URL中的百分号编码，用于日志打印。
     *
     * @param url URL字符串
     * @return 解码后的URL字符串
     */
    protected String decodeUrl(String url) {
        if (!JmModuleConfig.FLAG_DECODE_URL_WHEN_LOGGING || !url.contains("/search/")) {
            return url;
        }
        try {
            return URLDecoder.decode(url.replace("+", " "), StandardCharsets.UTF_8.name());
        } catch (UnsupportedEncodingException e) {
            return url; // Should not happen with UTF-8
        }
    }

    /**
     * 执行分页迭代的通用方法。
     *
     * @param initialParams 初始参数
     * @param initialPage   起始页码
     * @param getPageMethod 获取分页内容的方法
     * @param <T>           JmPageContent的子类型
     * @return 分页内容的迭代器
     */
    protected <T extends JmPageContent> Iterator<T> doPageIter(
            Map<String, Object> initialParams,
            int initialPage,
            Function<Map<String, Object>, T> getPageMethod) {

        return new Iterator<T>() {
            private int currentPage = initialPage;
            private int totalPages = Integer.MAX_VALUE;
            private boolean hasNextPage = true;

            @Override
            public boolean hasNext() {
                if (hasNextPage) {
                    if (currentPage <= totalPages) {
                        return true;
                    } else {
                        hasNextPage = false; // Reached the end
                        return false;
                    }
                }
                return false;
            }

            @Override
            public T next() {
                if (!hasNext()) {
                    throw new NoSuchElementException();
                }
                Map<String, Object> params = new HashMap<>(initialParams);
                params.put("page", currentPage);
                T pageContent = getPageMethod.apply(params);

                // 更新总页数
                if (totalPages == Integer.MAX_VALUE) {
                    totalPages = pageContent.getPageCount();
                }

                currentPage++;
                return pageContent;
            }
        };
    }

    @Override
    public Iterator<JmFavoritePage> favoriteFolderGen(int page, String orderBy, String folderId, String username) {
        Map<String, Object> params = new HashMap<>();
        params.put("orderBy", orderBy);
        params.put("folderId", folderId);
        params.put("username", username);
        return doPageIter(params, page, p -> getFavoriteFolder((int) p.get("page"), (String) p.get("orderBy"), (String) p.get("folderId"), (String) p.get("username")));
    }

    @Override
    public Iterator<JmSearchPage> searchGen(String searchQuery, int mainTag, int page, String orderBy, String time, String category, String subCategory) {
        Map<String, Object> params = new HashMap<>();
        params.put("searchQuery", searchQuery);
        params.put("mainTag", mainTag);
        params.put("orderBy", orderBy);
        params.put("time", time);
        params.put("category", category);
        params.put("subCategory", subCategory);
        return doPageIter(params, page, p -> search(
                (String) p.get("searchQuery"), (int) p.get("page"), (int) p.get("mainTag"),
                (String) p.get("orderBy"), (String) p.get("time"), (String) p.get("category"), (String) p.get("subCategory")));
    }

    @Override
    public Iterator<JmSearchPage> categoriesFilterGen(int page, String time, String category, String orderBy, String subCategory) {
        Map<String, Object> params = new HashMap<>();
        params.put("time", time);
        params.put("category", category);
        params.put("orderBy", orderBy);
        params.put("subCategory", subCategory);
        return doPageIter(params, page, p -> categoriesFilter(
                (int) p.get("page"), (String) p.get("time"), (String) p.get("category"),
                (String) p.get("orderBy"), (String) p.get("subCategory")));
    }

    /**
     * 模板方法：从缓存中获取数据，如果缓存未命中，则通过网络获取并存入缓存。
     *
     * @param cacheKey 缓存的唯一键。
     * @param networkFetcher 网络获取数据的逻辑，封装在Callable中。
     * @param <T> 返回的数据类型。
     * @return 缓存中或网络获取的数据。
     * @throws RuntimeException 如果网络获取失败。
     */
    @SuppressWarnings("unchecked")
    protected <T> T fetchFromCacheOrNetwork(String cacheKey, Callable<T> networkFetcher) throws RuntimeException{
        // 步骤 1: 检查缓存是否启用
        if (this.clientCache == null) {
            try {
                // 缓存未启用，直接执行网络请求
                return networkFetcher.call();
            } catch (Exception e) {
                // 将受检异常包装为运行时异常
                throw new RuntimeException("Network fetcher failed", e);
            }
        }

        // 步骤 2 & 3: 检查缓存，如果不存在则执行网络请求并存入缓存
        // computeIfAbsent 是线程安全的原子操作，完美实现此逻辑
        try {
            Object result = this.clientCache.computeIfAbsent(cacheKey, key -> {
                try {
                    // 该 Lambda 表达式只在缓存未命中时执行
                    return networkFetcher.call();
                } catch (Exception e) {
                    // 将受检异常包装为运行时异常
                    throw new RuntimeException("Network fetcher failed inside cache computation", e);
                }
            });

            // 步骤 4: 返回结果
            return (T) result;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}